﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using System.Collections.Specialized;
using ShredTheLog.Common.MVVM;
using System.Globalization;
using ShredTheLog.Model;
using System.ComponentModel.Composition;
using ShredTheLog.Api;
using ShredTheLog.Views;
using ShredTheLog.Api.DataContracts;
using ShredTheLog.Api.DataContracts.TextChat;
using System.Disposables;
using ShredTheLog.Controls;
using System.Windows.Threading;
using System.Net;
using System.Windows;

namespace ShredTheLog.ViewModels
{
    public sealed partial class TextChatViewModel : ViewModelBase
    {
        [Import]
        private IShredServices ShredServices { get; set; }

        [Import]
        private IConnectionSettingsSection ConnectionSettings { get; set; }

        [ImportMany]
        private Lazy<ICommandListItem, IMenuCommandMetadata>[] _joinPublicChatRoomCommands = null;
        public IEnumerable<Lazy<ICommandListItem, IMenuCommandMetadata>> JoinPublicChatRoomCommands 
        { 
            get 
            {
                Contract.Assume(_joinPublicChatRoomCommands != null);
                return from i in _joinPublicChatRoomCommands where i.Metadata.Menu == Menus.Rooms orderby i.Metadata.Order select i; 
            } 
        }

        [Import]
        private IShowLogInDialogCommand ShowLogInDialogCommand { get; set; }

        [ContractInvariantMethod]
        void ObjectInvariant()
        {
            Contract.Invariant(Users != null);
        }

        private CompositeDisposable _subscriptions = new CompositeDisposable();

        public TextChatViewModel()
        {
            Contract.Assume(ShredServices.TextChat == null);

            Users = new ReadOnlyObservableCollection<IShortUserInfo>(_internalUsers);

            Action<Exception> onError =
                ex =>
                {
                    var e = ex as WebException;
                    if (e != null && (e.Status == WebExceptionStatus.ConnectFailure || (e.Response is HttpWebResponse && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotFound)))
                        ShredServices.ShowError(() => ShredServices.GetLocalizedString("s_ConnectFailure"), ex);
                    else
                        ShredServices.ShowError(ex);
                };

            var credentials = ShowLogInDialogCommand.Credentials;
            var whenLoaded =
                from initialTextChatInfo in credentials != null ? ConnectionSettings.GetClient().JoinTextChat(credentials) : ConnectionSettings.GetClient().JoinTextChatAnonymously()
                select new { Credentials = credentials, InitialTextChatInfo = initialTextChatInfo };
            whenLoaded.ObserveOn(ShredServices.Dispatcher).Subscribe(
                i =>
                {
                    try
                    {
                        OnLoaded(i.Credentials, i.InitialTextChatInfo);
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                onError);
        }

        private void OnLoaded(ISessionCredentials credentials, IInitialTextChatInfo initialTextChatInfo)
        {
            Contract.Requires(initialTextChatInfo != null);
            Contract.Requires(initialTextChatInfo.OnlineUsers != null);
            Contract.Requires(initialTextChatInfo.Uid > 0);

            if (Status == TextChatStatus.Disposed)
                return;
            Contract.Assume(Status == TextChatStatus.Loading);

            Model = ConnectionSettings.GetClient().CreateTextChat(credentials, initialTextChatInfo);
            Status = TextChatStatus.Active;
            var whenErrorOccurred = Model.WhenErrorOccurred.ObserveOn(ShredServices.Dispatcher).Subscribe(
                exception =>
                {
                    try
                    {
                        Status = TextChatStatus.Disconnected;
                        ShredServices.ShowError(exception);
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenErrorOccurred);

            _internalUsers.AddRange(initialTextChatInfo.OnlineUsers);
            var whenUserEntersSubscription = Model.WhenUserEnters.ObserveOn(ShredServices.Dispatcher).Subscribe(
                userToAdd =>
                {
                    try
                    {
                        if (_internalUsers.Any(i => i.Uid == userToAdd.Uid))
                            throw new InvalidOperationException(String.Format("User {0:d} has been already added.", userToAdd.Uid));
                        _internalUsers.Add(userToAdd);
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenUserEntersSubscription);

            var whenUserLeavesSubscription = Model.WhenUserLeaves.ObserveOn(ShredServices.Dispatcher).Subscribe(
                userToDelete =>
                {
                    try
                    {
                        var u = _internalUsers.Single(i => i.Uid == userToDelete.Uid);
                        _internalUsers.Remove(u);
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenUserLeavesSubscription);

            var whenPrivateRequestReceivedSubscription = Model.WhenPrivateRequestReceived.ObserveOn(ShredServices.Dispatcher).Subscribe(
                caller =>
                {
                    try
                    {
                        new PrivateChatRequestView().Show(new PrivateChatRequestViewModel(Model, caller));
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenPrivateRequestReceivedSubscription);

            var whenPrivateRequestAnswerReceivedSubscription = Model.WhenPrivateRequestAnswerReceived.ObserveOn(ShredServices.Dispatcher).Subscribe(
                requestAnswer =>
                {
                    try
                    {
                        if (requestAnswer.Answer == RequestAnswer.Accept)
                        {
                            // Теоретически, ответ на приглашение в приват не мог прийти анонимному пользователю 
                            // (то есть, если поток выполнения пришел сюда, то это автоматически значит, что мы вошли в чат под своим именем, а не анонимно).
                            // Тем не менее, чтобы успокоить Code Contracts поставим здесь проверку...
                            if (ShowLogInDialogCommand.Credentials != null)
                                ShredServices.OpenTextChatRoom(TextChatHelper.FormatPrivateRoomName(ShowLogInDialogCommand.Credentials, requestAnswer));
                        }
                        else
                        {
                            var user = Users.Single(i => i.Uid == requestAnswer.Uid);
                            Func<string> messageFunc = () => String.Format(
                                CultureInfo.CurrentCulture,
                                ShredServices.GetLocalizedString("s_UserHasDeclinedRequest"),
                                user.Name);
                            new MessageDialogView().Show(new MessageDialogViewModel(messageFunc));
                        }
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenPrivateRequestAnswerReceivedSubscription);

            var whenSysMessageReceivedSubscription = Model.WhenSysMessageReceived.ObserveOn(ShredServices.Dispatcher).Subscribe(
                sysMessage =>
                {
                    try
                    {
                        var user = Users.Single(i => i.Uid == sysMessage.Uid);

                        Func<string> messageFunc = () => String.Format(
                            CultureInfo.CurrentCulture,
                            ShredServices.GetLocalizedString("s_SysMessageFromUser"),
                            user.Name, sysMessage.MessageText);
                        new MessageDialogView().Show(new MessageDialogViewModel(messageFunc));
                    }
                    catch (Exception ex)
                    {
                        ShredServices.ShowError(ex);
                    }
                },
                ShredServices.ShowError);
            _subscriptions.Add(whenSysMessageReceivedSubscription);

            Model.IsConnected = true;
        }

        public TextChatStatus Status 
        {
            get { return _status; }
            private set { _status = value; OnPropertyChanged(() => Status); OnPropertyChanged(() => IsLoading); }
        }
        private TextChatStatus _status = TextChatStatus.Loading;

        public bool IsLoading { get { return Status == TextChatStatus.Loading; } }

        public ITextChat Model { get; private set; }

        private ObservableCollection<IShortUserInfo> _internalUsers = new ObservableCollection<IShortUserInfo>();
        public ReadOnlyObservableCollection<IShortUserInfo> Users { get; private set; }

        private ICommand _closeCommand;
        public ICommand CloseCommand
        {
            get
            {
                if (_closeCommand == null)
                    _closeCommand = new DelegateCommand(Dispose);
                return _closeCommand;
            }
        }

        public event EventHandler Closed;

        public void Dispose()
        {
            Status = TextChatStatus.Disposed;
            Application.Current.Windows.OfType<TextChatRoomView>().ToArray().ForEach(room => room.Close());
            _subscriptions.Dispose();
            if (Model != null)
                Model.Dispose();
            if (Closed != null)
                Closed(this, EventArgs.Empty);
        }
    }

    public enum TextChatStatus
    {
        Loading,
        Active,
        Disconnected,
        Disposed
    }
}
